package com.fasterxml.jackson.databind.node;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.ParserMinimalBase;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.databind.JsonNode;

/**
 * 添加 <code>getObject</code>方法
 */
public class TreeTraversingParser extends ParserMinimalBase {
    protected ObjectCodec _objectCodec;
    protected NodeCursor _nodeCursor;
    protected boolean _closed;

    public TreeTraversingParser(JsonNode n) {
        this(n, null);
    }

    public TreeTraversingParser(JsonNode n, ObjectCodec codec) {
        super(0);
        _objectCodec = codec;
        _nodeCursor = new NodeCursor.RootCursor(n, null);
    }

    @Override
    public void setCodec(ObjectCodec c) {
        _objectCodec = c;
    }

    @Override
    public ObjectCodec getCodec() {
        return _objectCodec;
    }

    @Override
    public Version version() {
        return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;
    }

    @Override
    public JacksonFeatureSet<StreamReadCapability> getReadCapabilities() {
        return DEFAULT_READ_CAPABILITIES;
    }

    @Override
    public void close() throws IOException {
        if (!_closed) {
            _closed = true;
            _nodeCursor = null;
            _currToken = null;
        }
    }

    @Override
    public JsonToken nextToken() throws IOException {
        _currToken = _nodeCursor.nextToken();
        if (_currToken == null) {
            _closed = true;
            return null;
        }
        switch (_currToken) {
            case START_OBJECT:
                _nodeCursor = _nodeCursor.startObject();
                break;
            case START_ARRAY:
                _nodeCursor = _nodeCursor.startArray();
                break;
            case END_OBJECT:
            case END_ARRAY:
                _nodeCursor = _nodeCursor.getParent();
            default:
        }
        return _currToken;
    }

    @Override
    public JsonParser skipChildren() {
        if (_currToken == JsonToken.START_OBJECT) {
            _nodeCursor = _nodeCursor.getParent();
            _currToken = JsonToken.END_OBJECT;
        } else if (_currToken == JsonToken.START_ARRAY) {
            _nodeCursor = _nodeCursor.getParent();
            _currToken = JsonToken.END_ARRAY;
        }
        return this;
    }

    @Override
    public boolean isClosed() {
        return _closed;
    }

    @Override
    public String currentName() {
        NodeCursor crsr = _nodeCursor;
        if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
            crsr = crsr.getParent();
        }
        return (crsr == null) ? null : crsr.getCurrentName();
    }

    @Deprecated
    @Override
    public String getCurrentName() {
        return currentName();
    }

    @Override
    public void overrideCurrentName(String name) {
        NodeCursor crsr = _nodeCursor;
        if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
            crsr = crsr.getParent();
        }
        if (crsr != null) {
            crsr.overrideCurrentName(name);
        }
    }

    @Override
    public JsonStreamContext getParsingContext() {
        return _nodeCursor;
    }

    @Override
    public JsonLocation currentLocation() {
        return JsonLocation.NA;
    }

    @Override
    public JsonLocation currentTokenLocation() {
        return JsonLocation.NA;
    }

    @Deprecated
    @Override
    public JsonLocation getTokenLocation() {
        return currentTokenLocation();
    }

    @Deprecated
    @Override
    public JsonLocation getCurrentLocation() {
        return currentLocation();
    }

    @Override
    public Object getObject() throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getText() {
        if (_currToken == null) {
            return null;
        }

        switch (_currToken) {
            case FIELD_NAME:
                return _nodeCursor.getCurrentName();
            case VALUE_STRING:
                return currentNode().textValue();
            case VALUE_NUMBER_INT:
            case VALUE_NUMBER_FLOAT:
                return String.valueOf(currentNode().numberValue());
            case VALUE_EMBEDDED_OBJECT:
                JsonNode n = currentNode();
                if (n != null && n.isBinary()) {
                    return n.asText();
                }
            default:
                return _currToken.asString();
        }
    }

    @Override
    public char[] getTextCharacters() {
        return getText().toCharArray();
    }

    @Override
    public int getTextLength() {
        return getText().length();
    }

    @Override
    public int getTextOffset() {
        return 0;
    }

    @Override
    public boolean hasTextCharacters() {
        return false;
    }

    @Override
    public NumberType getNumberType() throws IOException {
        JsonNode n = currentNumericNode();
        return (n == null) ? null : n.numberType();
    }

    @Override
    public NumberTypeFP getNumberTypeFP() throws IOException {
        NumberType nt = getNumberType();
        if (nt == NumberType.BIG_DECIMAL) {
            return NumberTypeFP.BIG_DECIMAL;
        }
        if (nt == NumberType.DOUBLE) {
            return NumberTypeFP.DOUBLE64;
        }
        if (nt == NumberType.FLOAT) {
            return NumberTypeFP.FLOAT32;
        }
        return NumberTypeFP.UNKNOWN;
    }

    @Override
    public BigInteger getBigIntegerValue() throws IOException {
        return currentNumericNode().bigIntegerValue();
    }

    @Override
    public BigDecimal getDecimalValue() throws IOException {
        return currentNumericNode().decimalValue();
    }

    @Override
    public double getDoubleValue() throws IOException {
        return currentNumericNode().doubleValue();
    }

    @Override
    public float getFloatValue() throws IOException {
        return (float) currentNumericNode().doubleValue();
    }

    @Override
    public int getIntValue() throws IOException {
        final NumericNode node = (NumericNode) currentNumericNode();
        if (!node.canConvertToInt()) {
            reportOverflowInt();
        }
        return node.intValue();
    }

    @Override
    public long getLongValue() throws IOException {
        final NumericNode node = (NumericNode) currentNumericNode();
        if (!node.canConvertToLong()) {
            reportOverflowLong();
        }
        return node.longValue();
    }

    @Override
    public Number getNumberValue() throws IOException {
        return currentNumericNode().numberValue();
    }

    @Override
    public Object getEmbeddedObject() {
        if (!_closed) {
            JsonNode n = currentNode();
            if (n != null) {
                if (n.isPojo()) {
                    return ((POJONode) n).getPojo();
                }
                if (n.isBinary()) {
                    return ((BinaryNode) n).binaryValue();
                }
            }
        }
        return null;
    }

    @Override
    public boolean isNaN() {
        if (!_closed) {
            JsonNode n = currentNode();
            if (n instanceof NumericNode) {
                return ((NumericNode) n).isNaN();
            }
        }
        return false;
    }

    @Override
    public byte[] getBinaryValue(Base64Variant b64variant) throws IOException {
        JsonNode n = currentNode();
        if (n != null) {
            if (n instanceof TextNode) {
                return ((TextNode) n).getBinaryValue(b64variant);
            }
            return n.binaryValue();
        }
        return null;
    }

    @Override
    public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException {
        byte[] data = getBinaryValue(b64variant);
        if (data != null) {
            out.write(data, 0, data.length);
            return data.length;
        }
        return 0;
    }

    protected JsonNode currentNode() {
        if (_closed || _nodeCursor == null) {
            return null;
        }
        return _nodeCursor.currentNode();
    }

    protected JsonNode currentNumericNode() throws JacksonException {
        JsonNode n = currentNode();
        if (n == null || !n.isNumber()) {
            JsonToken t = (n == null) ? null : n.asToken();
            throw _constructError("Current token (" + t + ") not numeric, cannot use numeric value accessors");
        }
        return n;
    }

    @Override
    protected void _handleEOF() {
        _throwInternal();
    }
}
